/*
 * Object   : CServer
 * Type     : Backend
 *
 * Narrator : Kamran H. Khan 
 * Email    : KAMRANHK@gmail.com
 * 
 * Copyleft : Attribution - CreativeCommons http://creativecommons.org/licenses/by-nc/3.0/
 *            This software is provided 'as-is', without any express or implied warranty.
 *            In no event will the author(s) be held liable for any damages arising from
 *            the use of this software.
 * 
 *            Permission is granted to anyone to use this software for any purpose,
 *            including commercial applications, and to alter it and redistribute it
 *            freely, subject to the following restrictions:
 * 
 *            1. The origin of this software must not be misrepresented; you must not
 *            claim that you wrote the original software. If you use this software
 *            in a product, an acknowledgment in the product documentation would be
 *            appreciated but is not required.
 * 
 *            2. Altered source versions must be plainly marked as such, and must not
 *            be misrepresented as being the original software.
 * 
 *            3. This notice may not be removed or altered from any source distribution.
 * 
 * Description: 
 * Provides TCPIP network server functionalities
 * 
 * Revision History:
 * 
 * Date(MMM/dd/yy)      Owner           Description
 * ----------------------------------------------------------------------------------------------------------------------------------------------
 * Oct/24/08            Kamran H. Khan  Added skeleton code
 * Oct/25/08            Kamran H. Khan  Added more methods
 *  
 * 
 * 
 * 
*/


using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.ComponentModel;
using System.Drawing;

namespace Maxima.Networker
{

    //[ToolboxBitmap(typeof(CServer))]
    [ToolboxBitmap(typeof(Bitmap), "MaximaLabs.Networker.Properties.Resources.ICS_host")]
    public class CServer : CNetMgr, IDisposable
    {
        public TcpListener m_theSvr;
        private bool m_bStopped;
        Thread m_thdListener;

        int MSG_SIZE_4KB = 4096;
        int NO_OF_MAX_CLIENTS_SUPPORTED = 10;

        private bool m_bIsConnected = false;
        public bool IsConnected
        {
            get { return m_bIsConnected; }
        }

        private string m_strIP;
        [Category("Settings")]
        [Description("IP Address")]
        [BrowsableAttribute(true)]
        public string IP
        {
            get { return m_strIP; }
            set { m_strIP = value; }
        }

        private int m_nPort;

        [Category("Settings")]
        [Description("Port")]
        [BrowsableAttribute(true)]
        public int Port
        {
            get { return m_nPort; }
            set { m_nPort = value; }
        }

        public string m_strStatus;

        private List<TcpClient> m_lstClients = new List<TcpClient>();

        [Category("Settings")]
        [Description("Provides the list of connected clients")]
        [BrowsableAttribute(true)]
        public List<TcpClient> ListOfClients
        {
            get { return m_lstClients; }
        }

        public delegate void StatusChangeEventHandler(string strData);
        public event StatusChangeEventHandler OnStatusChange;

       
        /// <summary>
        /// Initializes a new instance of the <see cref="CServer"/> class.
        /// </summary>
        /// <param name="strIP">The STR IP.</param>
        /// <param name="nPort">The n port.</param>
        public CServer(string strIP, int nPort)
            : base(strIP, nPort, false)
        {
            m_strIP = strIP;
            m_nPort = nPort;
            Log("Loading server...");
        }


        /// <summary>
        /// Starts this instance.
        /// </summary>
        public void Start()
        {
            //StartThread();

            m_bStopped = false;
            
            m_thdListener = new Thread(delegate() { Listen(); });
            m_thdListener.IsBackground = true;
            m_thdListener.Start();
         
            Log("Server started.");
        }

        /// <summary>
        /// Listens this instance.
        /// </summary>
        private void Listen()
        {
            try
            {
                m_theSvr = new TcpListener(IPAddress.Parse(m_strIP), Convert.ToInt32(m_nPort));
                m_theSvr.Start(NO_OF_MAX_CLIENTS_SUPPORTED); //the backlog, for now its 10
                m_bIsConnected = true;

                while (!m_bStopped)
                {
                    Log("Listening...");
                    if (m_theSvr == null)
                        break;

                    TcpClient thisClient = this.m_theSvr.AcceptTcpClient();
                    Log(thisClient.Client.LocalEndPoint.ToString() + ">>New entry... : [" + thisClient.Client.LocalEndPoint.ToString() + "]");
                    m_lstClients.Add(thisClient);

                    Thread thdManageClient = new Thread(new ParameterizedThreadStart(ManageClient));
                    thdManageClient.IsBackground = true;
                    thdManageClient.Start(thisClient);
                }
            }
            catch(Exception exc)
            {
                Error(exc.Message);
            }
        }

        /// <summary>
        /// Manages the client.
        /// </summary>
        /// <param name="theClient">The client.</param>
        private void ManageClient(object theClient)
        {
            TcpClient thisClient = (TcpClient)theClient;
            NetworkStream theComlayer = thisClient.GetStream();

           
            int nBytesRead = 0;
            ASCIIEncoding theEncoding = new ASCIIEncoding();
            while (true)
            {
                try
                {
                    byte[] bytMessage = new byte[MSG_SIZE_4KB];
                    nBytesRead = theComlayer.Read(bytMessage, 0, bytMessage.Length);
                    if (nBytesRead == 0)
                        continue;

                    
                    string strMsg = theEncoding.GetString(bytMessage);

                    //remove any non printable characters from the message.
                    try
                    {
                        if (strMsg.StartsWith("\0"))
                        {
                            strMsg = strMsg.Replace('\0', '?');
                        }
                        
                    }catch (Exception exc) { Error(exc.Message); }

                    
                    // Log(thisClient.Client.LocalEndPoint.ToString() + ">>RX: [" + strMsg + "]");
                    //Not sure, why was I logging this two times?! -- 1143a-2009-02-20.
                    Log("\r\nRX: MSG[" + thisClient.Client.RemoteEndPoint.ToString() + "]" + strMsg);
                    Log("MSG[" + thisClient.Client.RemoteEndPoint.ToString() + "]" + strMsg);
                    if (strMsg.StartsWith("logout"))//Check if its a logout intimation.
                    {
                        bool bRemoved = m_lstClients.Remove(thisClient); //remove from the list
                        break;
                    }
                }
                catch (Exception exc)
                {
                    Error(exc.Message);
                    break;
                }

                //if (nBytesRead == 0)
                //    break;//clients wants to disconnect;

            }

        }

        /// <summary>
        /// Errors the specified STR MSG.
        /// </summary>
        /// <param name="strMsg">The STR MSG.</param>
        private void Error(string strMsg)
        {
            Log("ERROR: " + strMsg);
        }



        /// <summary>
        /// Logs the specified STR MSG.
        /// </summary>
        /// <param name="strMsg">The STR MSG.</param>
        private void Log(string strMsg)
        {
            m_strStatus =strMsg;// "\r\nSVR:" + 
            //OnStatusChange = new StatusChangeEventHandler();
            if(OnStatusChange != null)
                OnStatusChange(m_strStatus);
        }


        /// <summary>
        /// Sends the specified STR data.
        /// </summary>
        /// <param name="strData">The STR data.</param>
        /// <returns></returns>
        public int Send(string strData)
        {
            int nBytesSent = 0;
            if (m_theSvr == null)
                return nBytesSent;

            try
            {
                int nIndexStart = strData.IndexOf("[");
                int nIndexEnd = strData.IndexOf("]");
                string strMsg = strData.Substring(1, nIndexEnd -1);

                foreach (TcpClient theClient in m_lstClients)
                {
                    try
                    {
                        if (!theClient.Client.RemoteEndPoint.ToString().Equals(strMsg))
                            continue;

                        if (theClient.Connected)
                        {
                            if (theClient.Client.Connected)
                            {
                                nBytesSent = theClient.Client.Send(System.Text.ASCIIEncoding.ASCII.GetBytes(strData));
                                Log("\r\nTX: " + " [" + theClient.Client.RemoteEndPoint.ToString() + "] " + strData);
                                break;
                            }
                        }
                    }
                    catch (Exception exc)
                    {
                        Error(exc.Message);
                    }
                }
            }
            catch(Exception exc)
            {
                Error(exc.Message);
            }

            return nBytesSent;
        }



        /// <summary>
        /// Stops this instance.
        /// </summary>
        public void Stop()
        {
            Send("Shutting down server!");

            if (m_thdListener != null)
            {
                m_thdListener.Abort();
                m_thdListener = null;

                m_theSvr.Stop();
                m_theSvr = null;

                if (m_lstClients != null)
                {
                    m_lstClients.Clear();
                }
            }
        }



        #region IDisposable Members

        void IDisposable.Dispose()
        {
            m_theSvr.Server.Close();
            m_lstClients.Clear();
            m_lstClients = null;
            
        }

        #endregion
    }
}
